﻿using MultiLang;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

using JetBrains.Annotations;

using Rsdn.SmartApp;

namespace Rsdn.Janus
{
    internal static class XmlMenuLoader
    {
        private const string _menuDescriptorSchemaResource =
            "Rsdn.Janus.CoreServices.Menu.JanusMenuDescriptor.xsd";   //MLHIDE
        private const string _menuDescriptorSchemaUri =
            "http://rsdn.ru/projects/Janus/JanusMenuDescriptor.xsd";  //MLHIDE

        private static readonly XmlReaderSettings _readerSettings;

        static XmlMenuLoader()
        {
            _readerSettings = new XmlReaderSettings { ValidationType = ValidationType.Schema };
            _readerSettings.Schemas.Add(CommonTypesSchema.Schema);
            _readerSettings.Schemas.Add(
                XmlSchema.Read(
                    typeof(XmlMenuLoader)
                        .Assembly
                        .GetRequiredResourceStream(_menuDescriptorSchemaResource),
                    null));

        }

        public static IMenuRoot LoadMenu(
            [NotNull] IServiceProvider serviceProvider,
            [NotNull] Stream xmlMenuStream,
            [NotNull] Func<string, string> resourceStringGetter)
        {
            if (serviceProvider == null)
                throw new ArgumentNullException("serviceProvider");   //MLHIDE
            if (xmlMenuStream == null)
                throw new ArgumentNullException("xmlMenuStream");     //MLHIDE
            if (resourceStringGetter == null)
                throw new ArgumentNullException("resourceStringGetter"); //MLHIDE

            return LoadMenuRoot(
                serviceProvider,
                XElement.Load(XmlReader.Create(xmlMenuStream, _readerSettings)),
                resourceStringGetter);
        }

        private static IMenuRoot LoadMenuRoot(
            IServiceProvider serviceProvider,
            XElement rootElement,
            Func<string, string> resourceStringGetter)
        {
            if (rootElement.Name == XName.Get("root", _menuDescriptorSchemaUri)) //MLHIDE
                return new MenuRoot(
                    (string)rootElement.Attribute(XName.Get("name")), //MLHIDE
                    rootElement
                        .Elements()
                        .Select(sub => LoadMenuGroup(serviceProvider, sub, resourceStringGetter)));

            throw new ApplicationException(ml.ml_string(120, "Неизвестный элемент '{0}'.").FormatStr(rootElement));
        }

        private static IMenuGroup LoadMenuGroup(
            IServiceProvider serviceProvider,
            XElement groupElement,
            Func<string, string> resourceStringGetter)
        {
            return new MenuGroup(
                (string)groupElement.Attribute(XName.Get("name")),    //MLHIDE
                groupElement
                    .Elements()
                    .Select(itemElement => LoadMenuItem(serviceProvider, itemElement, resourceStringGetter)),
                (int)groupElement.Attribute(XName.Get("orderIndex"))); //MLHIDE
        }

        private static IMenuItem LoadMenuItem(
            IServiceProvider serviceProvider,
            XElement itemElement,
            Func<string, string> resourceStringGetter)
        {
            if (itemElement.Name == XName.Get("menu", _menuDescriptorSchemaUri)) //MLHIDE
                return new Menu(
                    (string)itemElement.Attribute(XName.Get("name")), //MLHIDE
                    itemElement.Elements().Select(
                        sub => LoadMenuGroup(serviceProvider, sub, resourceStringGetter)),
                    resourceStringGetter((string)itemElement.Attribute(XName.Get("textResource"))), //MLHIDE
                    (string)itemElement.Attribute(XName.Get("image")), //MLHIDE
                    (string)itemElement.Attribute(XName.Get("description")), //MLHIDE
                    (MenuItemDisplayStyle)Enum.Parse(
                        typeof(MenuItemDisplayStyle),
                        (string)itemElement.Attribute(XName.Get("displayStyle"))), //MLHIDE
                    (int)itemElement.Attribute(XName.Get("orderIndex"))); //MLHIDE

            if (itemElement.Name == XName.Get("menuCommand", _menuDescriptorSchemaUri)) //MLHIDE
            {
                var commandName = (string)itemElement.Attribute(XName.Get("command")); //MLHIDE

                string text;
                string description;
                GetMenuCommandTextAndDescription(
                    serviceProvider,
                    commandName,
                    (string)itemElement.Attribute(XName.Get("textResource")), //MLHIDE
                    (string)itemElement.Attribute(XName.Get("description")), //MLHIDE
                    resourceStringGetter,
                    out text,
                    out description);

                return new MenuCommand(
                    commandName,
                    LoadCommandParameters(itemElement),
                    text,
                    (string)itemElement.Attribute(XName.Get("image")), //MLHIDE
                    description,
                    (MenuItemDisplayStyle)Enum.Parse(
                        typeof(MenuItemDisplayStyle),
                        (string)itemElement.Attribute(XName.Get("displayStyle"))), //MLHIDE
                    (int)itemElement.Attribute(XName.Get("orderIndex"))); //MLHIDE
            }

            if (itemElement.Name == XName.Get("splitButton", _menuDescriptorSchemaUri)) //MLHIDE
            {
                var commandName = (string)itemElement.Attribute(XName.Get("command")); //MLHIDE
                string text;
                string description;
                GetMenuCommandTextAndDescription(
                    serviceProvider,
                    commandName,
                    (string)itemElement.Attribute(XName.Get("textResource")), //MLHIDE
                    (string)itemElement.Attribute(XName.Get("description")), //MLHIDE
                    resourceStringGetter,
                    out text,
                    out description);

                return new MenuSplitButton(
                    (string)itemElement.Attribute(XName.Get("name")), //MLHIDE
                    itemElement
                        .Elements(XName.Get("group", _menuDescriptorSchemaUri)) //MLHIDE
                        .Select(sub => LoadMenuGroup(serviceProvider, sub, resourceStringGetter)),
                    commandName,
                    LoadCommandParameters(itemElement),
                    text,
                    (string)itemElement.Attribute(XName.Get("image")), //MLHIDE
                    description,
                    (MenuItemDisplayStyle)Enum.Parse(
                        typeof(MenuItemDisplayStyle),
                        (string)itemElement.Attribute(XName.Get("displayStyle"))), //MLHIDE
                    (int)itemElement.Attribute(XName.Get("orderIndex"))); //MLHIDE
            }

            if (itemElement.Name == XName.Get("menuCheckCommand", _menuDescriptorSchemaUri)) //MLHIDE
            {
                var checkCommandElement = itemElement.Element(
                    XName.Get("checkCommand", _menuDescriptorSchemaUri)); //MLHIDE
                var checkCommandName = (string)checkCommandElement.Attribute(XName.Get("name")); //MLHIDE
                var checkCommandParameters = LoadCommandParameters(checkCommandElement);

                var uncheckCommandElement = itemElement.Element(
                    XName.Get("uncheckCommand", _menuDescriptorSchemaUri)); //MLHIDE
                var uncheckCommandName = (string)uncheckCommandElement.Attribute(XName.Get("name")); //MLHIDE
                var uncheckCommandParameters = LoadCommandParameters(uncheckCommandElement);

                return new MenuCheckCommand(
                    (string)itemElement.Attribute(XName.Get("checkStateName")), //MLHIDE
                    checkCommandName,
                    checkCommandParameters,
                    uncheckCommandName,
                    uncheckCommandParameters,
                    resourceStringGetter((string)itemElement.Attribute(XName.Get("textResource"))), //MLHIDE
                    (string)itemElement.Attribute(XName.Get("image")), //MLHIDE
                    (string)itemElement.Attribute(XName.Get("description")), //MLHIDE
                    (MenuItemDisplayStyle)Enum.Parse(
                        typeof(MenuItemDisplayStyle),
                        (string)itemElement.Attribute(XName.Get("displayStyle"))), //MLHIDE
                    (int)itemElement.Attribute(XName.Get("orderIndex"))); //MLHIDE
            }

            throw new ApplicationException(
                ml.ml_string(120, "Неизвестный элемент '{0}'.").FormatStr(itemElement));
        }

        private static void GetMenuCommandTextAndDescription(
            IServiceProvider serviceProvider,
            string commandName,
            string textResource,
            string descriptionResource,
            Func<string, string> resourceStringGetter,
            out string text,
            out string description)
        {
            ICommandService commandService = serviceProvider.GetRequiredService<ICommandService>();

            ICommandInfo command = commandService.GetCommandInfo(commandName);

            text = textResource != null
                ? resourceStringGetter(textResource)
                : command.DisplayName;

            if (command.Type == CommandType.RequiresInteraction)
                text += "...";                                        //MLHIDE

            if (descriptionResource != null)
                description = resourceStringGetter(descriptionResource);
            else if (textResource == null)
                description = command.Description;
            else
                description = null;
        }

        private static IDictionary<string, object> LoadCommandParameters(XContainer xe)
        {
            return
                xe
                    .Elements(XName.Get("parameter", _menuDescriptorSchemaUri)) //MLHIDE
                    .ToDictionary(
                        parameter => (string)parameter.Attribute(XName.Get("name")), //MLHIDE
                        parameter => (object)parameter.Value,
                        StringComparer.OrdinalIgnoreCase)
                    .AsReadOnly();
        }
    }
}